package sim16;
import java.io.File;
import java.io.FileNotFoundException;


/**
 * IDStage: The instruction decoder pipe stage. Contains the register file and instruction decoder.
 * On a cycle(), gets instruction from IFStage and decodes it. Checks for hazards by asking the 
 * HazardController about each operand. Forwards data if available. Stalls if necessary. Gets
 * operands from register file if no hazards. On a branch, if taken, updates PC of IF stage and
 * causes a bubble to be inserted.
 * @author Piotr
 *
 */
public class IDStage extends PipeStage {
	
	private final int numberOfPipes;
	
	Instruction[] instr;	//Queue of instructions in IDStage
	
	int instrCount; //Number of instructions in queue
	
	int readyCount; //Count of instructions that can be dispatched to EXStage
	
	private RegisterFile regFile;
	private InstructionDecoder instrDec;
	
	int fetchCnt;
	int nextFetchCnt;
	
	IDStage(int numOfPipes) {
		super(0);
		numberOfPipes = numOfPipes;
		instr = new Instruction[2*numberOfPipes];
		regFile = new RegisterFile();
		instrDec = new InstructionDecoder();
		fetchCnt = nextFetchCnt = numberOfPipes;
		instrCount = 0;
	}
	
	IDStage(File dataFile, int numOfPipes) {
		super(0);
		numberOfPipes = numOfPipes;
		instr = new Instruction[2*numberOfPipes];
		try {
			regFile = new RegisterFile(dataFile);
		} catch (FileNotFoundException e) {
			System.out.println("Error reading " + dataFile 
					+ ". Did not initialize register file.");
			regFile = new RegisterFile();
		}
		instrDec = new InstructionDecoder();
		fetchCnt = nextFetchCnt = numberOfPipes;
		instrCount = 0;
	}
	
	
	
	/**
	 * cycle method. Gets instruction from IFStage. 
	 */
	int cycle(PipeStage prevStage) throws InvalidInstructionException {
		if (!stalled) {
			//Remove the old instructions from the head of the queue
			int ii;
			for (ii = 0; ii < 2*numberOfPipes - readyCount; ii++) {
				instr[ii] = instr[ii+readyCount];
			}
			for (; ii < 2*numberOfPipes; ii++) {
				instr[ii] = null;
			}
			instrCount -= readyCount;
			//Put the new instructions at the end of the queue
			for (ii = 0; ii < fetchCnt; ii++) {
				instr[instrCount++] = ((IFStage)prevStage).passInstruction(ii);
			}
			doCycle();
		} else if (causedStall) {
			causedStall = false;
			stalled = false;
			doCycle();
		} else {
			stalled = false;
		}
		//Count the number of real instructions in the queue
		int insCnt = 0;
		for (int ii = 0; ii < instrCount; ii++) {
			if (instr[ii] != null && !instr[ii].isNop()) {
				insCnt++;
			}
		}
		return insCnt;
	}
	
	/**
	 * Private method to help do cycle(). Is called once the queue has the new instructions
	 * from the IFStage added to the end.
	 * @throws InvalidInstructionException
	 */
	private void doCycle() throws InvalidInstructionException {
		readyCount = 0;
		boolean branched = false;
		boolean blocked = false;	//Flag to indicate when no more instructions can be
									//dispatched
		while (!blocked && readyCount < instrCount && readyCount < numberOfPipes) {
			if (instr[readyCount] == null) {
				readyCount++;		//We're not skipping bubbles, just put them in the pipe
			} else {
				if (instrDec.decodeInstruction(instr[readyCount]) == 1) {
					throw new InvalidInstructionException(instr[readyCount].address);
				}
				blocked = checkForBlocking(readyCount);
				if (!blocked) {
					readOperands(readyCount);
					if (instr[readyCount].iClass.equals(InstructionClass.BRANCH)) {
						if (instr[readyCount].arg1Ready) {	//Check if the branch would cause a stall.
							branched = blocked = doBranch(readyCount);
						} else {	//This branch would cause a stall, so don't dispatch it
							readyCount--;
							blocked = true;
						}
					}
					readyCount++;
					//if branching, need to clean out rest of queue
					if (branched) {
						for (int ii = readyCount; ii < numberOfPipes*2; ii++) {
							instr[ii] = null;
						}
						instrCount = readyCount;
					}
				}
			}
		}
		fetchCnt = nextFetchCnt;
		if (!branched) {
			int nextICnt = instrCount - readyCount + fetchCnt;
			nextFetchCnt = (nextICnt < numberOfPipes ? numberOfPipes : numberOfPipes*2-nextICnt);
			Sim.pipe.ifs.slowDown(nextFetchCnt);
		} else {
			nextFetchCnt = numberOfPipes;
		}
	}

	/**
	 * Function to read operands. Checks for hazards by asking the hazard controller.
	 * If a hazard exists, checks if a value can be forwarded immediately.
	 * @param num	The index of the instruction in instr[]
	 * @throws InvalidInstructionException
	 */
	private void readOperands(int num) throws InvalidInstructionException {
		try {
			if (instr[num].arg1Reg != -1) {
				if (Sim.pipe.hzc.checkForHazard(instr[num].arg1Reg, num)) {
					Integer i = Sim.pipe.hzc.tryForwarding(instr[num].arg1Reg, this.getClass(), num);
					if (i != null) {
						instr[num].arg1Val = i;
						instr[num].arg1Ready = true;
					} else {
						instr[num].arg1Ready = false;
					}
				} else {
					instr[num].arg1Val = regFile.readReg(instr[num].arg1Reg);
					instr[num].arg1Ready = true;
				}
			}
			if (instr[num].arg2Reg != -1) {
				if (Sim.pipe.hzc.checkForHazard(instr[num].arg2Reg, num)) {
					Integer i = Sim.pipe.hzc.tryForwarding(instr[num].arg2Reg, this.getClass(), num);
					if (i != null) {
						instr[num].arg2Val = i;
						instr[num].arg2Ready = true;
					} else {
						instr[num].arg2Ready = false;
					}
				} else {
					instr[num].arg2Val = regFile.readReg(instr[num].arg2Reg);
					instr[num].arg2Ready = true;
				}
			}
		} catch (RegisterIndexOutOfBoundsException e) {
			throw new InvalidInstructionException(instr[num].address);
		}
	}
	
	/**
	 * Helper method, checks whether the instruction indexed by num has any dependences
	 * with the instructions above it in the queue (and therefore cannot be scheduled
	 * in the same cycle.
	 */
	private boolean checkForBlocking(int num) {
		boolean blocking = false;
		for (int ii = 0; ii < num; ii++) {
			if (instr[ii] != null) {
				int a1 = instr[num].arg1Reg;
				if (a1 != -1 && a1 != 0) {
					if (a1 == instr[ii].dest1Reg || a1 == instr[ii].dest2Reg) {
						return true;
					}
				}
				a1 = instr[num].arg2Reg;
				if (a1 != -1 && a1 != 0) {
					if (a1 == instr[ii].dest1Reg || a1 == instr[ii].dest2Reg) {
						return true;
					}
				}
			}
		}
		return blocking;
	}
	
	/**
	 * Determines whether the branch is taken and performs it if it is. Causes a stall
	 * if a value is not available.
	 * @param num the index of the instruction in instr[]
	 * @return true if the branch is taken
	 */
	private boolean doBranch(int num) {
		if (!instr[num].arg1Ready) {
			Sim.pipe.hzc.stallPipe(this.getClass(), 0);
		} else {
			switch (instr[num].iType) {
			case BEZ:
				if (instr[num].arg1Val == 0) {
					instr[num].branchTaken = true;
					instr[num].memAddr = instr[num].address + instr[num].arg2Val + 1;
					Sim.pipe.ifs.branch(instr[num].memAddr);
				}
				break;
			case BLZ:
				if (instr[num].arg1Val < 0) {
					instr[num].branchTaken = true;
					instr[num].memAddr = instr[num].address + instr[num].arg2Val + 1;
					Sim.pipe.ifs.branch(instr[num].memAddr);
				}
				break;
			case BGZ:
				if (instr[num].arg1Val > 0) {
					instr[num].branchTaken = true;
					instr[num].memAddr = instr[num].address + instr[num].arg2Val + 1;
					Sim.pipe.ifs.branch(instr[num].memAddr);
				}
				break;
			case BLEZ:
				if (instr[num].arg1Val <= 0) {
					instr[num].branchTaken = true;
					instr[num].memAddr = instr[num].address + instr[num].arg2Val + 1;
					Sim.pipe.ifs.branch(instr[num].memAddr);
				}
				break;
			case BNEZ:
				if (instr[num].arg1Val != 0) {
					instr[num].branchTaken = true;
					instr[num].memAddr = instr[num].address + instr[num].arg2Val + 1;
					Sim.pipe.ifs.branch(instr[num].memAddr);
				}
				break;
			case JALR:
				instr[num].branchTaken = true;
				instr[num].memAddr = instr[num].arg1Val;
				Sim.pipe.ifs.branch(instr[num].memAddr);
				break;
			default:
				instr[num].branchTaken = false;
			}
		}
		return instr[num].branchTaken;
	}
	
	/**
	 * Writes to the register file
	 * @param regAddr 
	 * @param value
	 */
	void writeBack(int index, int value) throws RegisterIndexOutOfBoundsException {
		regFile.writeReg(index, value);
	}
	
	/**
	 * Returns a dump of the register file.
	 * @return
	 */
	String dumpRegisters() {
		return regFile.toString();
	}
	
	/**
	 * Returns the instruction currently in the pipe stage or a null.
	 * @param pipeID the number of the instruction to get.
	 * 			Must be between [0..2*numberOfPipes - 1]
	 * @return The instruction currently in the pipe stage, null if a bubble.
	 */
	public Instruction getInstruction(int pipeID) {
		return instr[pipeID];
	}
	
	/**
	 * Like getInstruction(), but creates bubbles during a stall. Use for passing
	 * instructions between pipe stages. Uses readyCount to only pass ready instructions.
	 * @return The instruction in the pipe stage, or a null if the stage is stalled.
	 */
	public Instruction passInstruction(int pipeID) {
		if (stalled) {
			return null;
		} else if (pipeID >= 0 && pipeID < readyCount) {
			return instr[pipeID];
		} else {
			return null;
		}
	}
	
}
